// Copyright 2018 The Grafeas Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package vulnerability

import (
	"testing"

	gpb "github.com/grafeas/grafeas/proto/v1/grafeas_go_proto"
)

func TestValidateNote(t *testing.T) {
	tests := []struct {
		desc     string
		v        *gpb.VulnerabilityNote
		wantErrs bool
	}{
		{
			desc: "nil details, want error(s)",
			v: &gpb.VulnerabilityNote{
				Severity: gpb.Severity_CRITICAL,
				Details:  nil,
			},
			wantErrs: true,
		},
		{
			desc: "empty details, want error(s)",
			v: &gpb.VulnerabilityNote{
				Severity: gpb.Severity_CRITICAL,
				Details:  []*gpb.VulnerabilityNote_Detail{},
			},
			wantErrs: true,
		},
		{
			desc: "nil detail, want error(s)",
			v: &gpb.VulnerabilityNote{
				Severity: gpb.Severity_CRITICAL,
				Details: []*gpb.VulnerabilityNote_Detail{
					nil,
				},
			},
			wantErrs: true,
		},
		{
			desc: "invalid vulnerability detail, want error(s)",
			v: &gpb.VulnerabilityNote{
				Severity: gpb.Severity_CRITICAL,
				Details: []*gpb.VulnerabilityNote_Detail{
					&gpb.VulnerabilityNote_Detail{},
				},
			},
			wantErrs: true,
		},
		{
			desc: "valid vulnerability, want success",
			v: &gpb.VulnerabilityNote{
				Severity: gpb.Severity_CRITICAL,
				Details: []*gpb.VulnerabilityNote_Detail{
					&gpb.VulnerabilityNote_Detail{
						SeverityName:    "LOW",
						AffectedCpeUri:  "cpe:/o:debian:debian_linux:7",
						AffectedPackage: "debian",
						AffectedVersionStart: &gpb.Version{
							Name: "1.1.2",
							Kind: gpb.Version_NORMAL,
						},
						AffectedVersionEnd: &gpb.Version{
							Name: "1.1.2",
							Kind: gpb.Version_NORMAL,
						},
						FixedVersion: &gpb.Version{
							Kind: gpb.Version_MAXIMUM,
						},
					},
				},
			},
			wantErrs: false,
		},
	}

	for _, tt := range tests {
		errs := ValidateNote(tt.v)
		t.Logf("%q: error(s): %v", tt.desc, errs)
		if len(errs) == 0 && tt.wantErrs {
			t.Errorf("%q: ValidateNote(%+v): got success, want error(s)", tt.desc, tt.v)
		}
		if len(errs) > 0 && !tt.wantErrs {
			t.Errorf("%q: ValidateNote(%+v): got error(s) %v, want success", tt.desc, tt.v, errs)
		}
	}
}

func TestValidateVulnerabilityDetail(t *testing.T) {
	tests := []struct {
		desc     string
		vd       *gpb.VulnerabilityNote_Detail
		wantErrs bool
	}{
		{
			desc: "missing affected CPE URI, want error(s)",
			vd: &gpb.VulnerabilityNote_Detail{
				SeverityName:    "LOW",
				AffectedPackage: "foobar",
			},
			wantErrs: true,
		},
		{
			desc: "missing affected package, want error(s)",
			vd: &gpb.VulnerabilityNote_Detail{
				SeverityName:   "LOW",
				AffectedCpeUri: "cpe:/o:debian:debian_linux:7",
			},
			wantErrs: true,
		},
		{
			desc: "invalid affected version start, want error(s)",
			vd: &gpb.VulnerabilityNote_Detail{
				SeverityName:         "LOW",
				AffectedCpeUri:       "cpe:/o:debian:debian_linux:7",
				AffectedPackage:      "foobar",
				AffectedVersionStart: &gpb.Version{},
				AffectedVersionEnd: &gpb.Version{
					Kind: gpb.Version_MINIMUM,
				},
				FixedVersion: &gpb.Version{
					Kind: gpb.Version_MAXIMUM,
				},
			},
			wantErrs: true,
		},
		{
			desc: "invalid affected version end, want error(s)",
			vd: &gpb.VulnerabilityNote_Detail{
				SeverityName:       "LOW",
				AffectedCpeUri:     "cpe:/o:debian:debian_linux:7",
				AffectedPackage:    "foobar",
				AffectedVersionEnd: &gpb.Version{},
				AffectedVersionStart: &gpb.Version{
					Kind: gpb.Version_MINIMUM,
				},
				FixedVersion: &gpb.Version{
					Kind: gpb.Version_MAXIMUM,
				},
			},
			wantErrs: true,
		},
		{
			desc: "invalid fixed version, want error(s)",
			vd: &gpb.VulnerabilityNote_Detail{
				SeverityName:    "LOW",
				AffectedCpeUri:  "cpe:/o:debian:debian_linux:7",
				AffectedPackage: "foobar",
				AffectedVersionStart: &gpb.Version{
					Kind: gpb.Version_MINIMUM,
				},
				AffectedVersionEnd: &gpb.Version{
					Kind: gpb.Version_MINIMUM,
				},
				FixedVersion: &gpb.Version{},
			},
			wantErrs: true,
		},
		{
			desc: "fixed version NORMAL, but missing fixed CPE URI, want error(s)",
			vd: &gpb.VulnerabilityNote_Detail{
				SeverityName:    "LOW",
				AffectedCpeUri:  "cpe:/o:debian:debian_linux:7",
				AffectedPackage: "foobar",
				AffectedVersionStart: &gpb.Version{
					Kind: gpb.Version_MINIMUM,
				},
				AffectedVersionEnd: &gpb.Version{
					Kind: gpb.Version_MINIMUM,
				},
				FixedPackage: "foobar",
				FixedVersion: &gpb.Version{
					Kind: gpb.Version_NORMAL,
					Name: "1.7.2",
				},
			},
			wantErrs: true,
		},
		{
			desc: "fixed version NORMAL, but missing fixed package, want error(s)",
			vd: &gpb.VulnerabilityNote_Detail{
				SeverityName:    "LOW",
				AffectedCpeUri:  "cpe:/o:debian:debian_linux:7",
				AffectedPackage: "foobar",
				AffectedVersionStart: &gpb.Version{
					Kind: gpb.Version_MINIMUM,
				},
				AffectedVersionEnd: &gpb.Version{
					Kind: gpb.Version_MINIMUM,
				},
				FixedCpeUri: "cpe:/o:debian:debian_linux:8",
				FixedVersion: &gpb.Version{
					Kind: gpb.Version_NORMAL,
					Name: "1.7.2",
				},
			},
			wantErrs: true,
		},
		{
			desc: "valid vulnerability details with fixed version NORMAL, want success",
			vd: &gpb.VulnerabilityNote_Detail{
				SeverityName:    "LOW",
				AffectedCpeUri:  "cpe:/o:debian:debian_linux:7",
				AffectedPackage: "foobar",
				AffectedVersionStart: &gpb.Version{
					Kind: gpb.Version_MINIMUM,
				},
				AffectedVersionEnd: &gpb.Version{
					Kind: gpb.Version_MINIMUM,
				},
				FixedCpeUri:  "cpe:/o:debian:debian_linux:7",
				FixedPackage: "foobar",
				FixedVersion: &gpb.Version{
					Kind: gpb.Version_NORMAL,
					Name: "1.7.2",
				},
			},
			wantErrs: false,
		},
		{
			desc: "valid vulnerability details with fixed version MAXIMUM, want success",
			vd: &gpb.VulnerabilityNote_Detail{
				SeverityName:    "LOW",
				AffectedCpeUri:  "cpe:/o:debian:debian_linux:7",
				AffectedPackage: "foobar",
				AffectedVersionStart: &gpb.Version{
					Kind: gpb.Version_MINIMUM,
				},
				AffectedVersionEnd: &gpb.Version{
					Kind: gpb.Version_MINIMUM,
				},
				FixedVersion: &gpb.Version{
					Kind: gpb.Version_MAXIMUM,
				},
			},
			wantErrs: false,
		},
	}

	for _, tt := range tests {
		errs := validateVulnerabilityDetail(tt.vd)
		t.Logf("%q: error(s): %v", tt.desc, errs)
		if len(errs) == 0 && tt.wantErrs {
			t.Errorf("%q: validateVulnerabilityDetail(%+v): got success, want error(s)", tt.desc, tt.vd)
		}
		if len(errs) > 0 && !tt.wantErrs {
			t.Errorf("%q: validateVulnerabilityDetail(%+v): got error(s) %v, want success", tt.desc, tt.vd, errs)
		}
	}
}

func TestValidateOccurrence(t *testing.T) {
	tests := []struct {
		desc     string
		d        *gpb.VulnerabilityOccurrence
		wantErrs bool
	}{
		{
			desc: "nil package issue, want error(s)",
			d: &gpb.VulnerabilityOccurrence{
				Severity:     gpb.Severity_CRITICAL,
				PackageIssue: nil,
			},
			wantErrs: true,
		},
		{
			desc: "empty package issue, want error(s)",
			d: &gpb.VulnerabilityOccurrence{
				Severity:     gpb.Severity_CRITICAL,
				PackageIssue: []*gpb.VulnerabilityOccurrence_PackageIssue{},
			},
			wantErrs: true,
		},
		{
			desc: "nil package issue element, want error(s)",
			d: &gpb.VulnerabilityOccurrence{
				Severity: gpb.Severity_CRITICAL,
				PackageIssue: []*gpb.VulnerabilityOccurrence_PackageIssue{
					nil,
				},
			},
			wantErrs: true,
		},
		{
			desc: "invalid package issue element, want error(s)",
			d: &gpb.VulnerabilityOccurrence{
				Severity: gpb.Severity_CRITICAL,
				PackageIssue: []*gpb.VulnerabilityOccurrence_PackageIssue{
					&gpb.VulnerabilityOccurrence_PackageIssue{},
				},
			},
			wantErrs: true,
		},
		{
			desc: "valid package issue, want success",
			d: &gpb.VulnerabilityOccurrence{
				PackageIssue: []*gpb.VulnerabilityOccurrence_PackageIssue{
					{
						AffectedCpeUri:  "cpe:/o:debian:debian_linux:7",
						AffectedPackage: "foobar",
						AffectedVersion: &gpb.Version{
							Name: "1.1.2",
							Kind: gpb.Version_NORMAL,
						},
						FixedCpeUri:  "cpe:/o:debian:debian_linux:7",
						FixedPackage: "foobar",
						FixedVersion: &gpb.Version{
							Name: "2.0.0",
							Kind: gpb.Version_NORMAL,
						},
					},
				},
			},
			wantErrs: false,
		},
	}

	for _, tt := range tests {
		errs := ValidateOccurrence(tt.d)
		t.Logf("%q: error(s): %v", tt.desc, errs)
		if len(errs) == 0 && tt.wantErrs {
			t.Errorf("%q: ValidateOccurrence(%+v): got success, want error(s)", tt.desc, tt.d)
		}
		if len(errs) > 0 && !tt.wantErrs {
			t.Errorf("%q: ValidateOccurrence(%+v): got error(s) %v, want success", tt.desc, tt.d, errs)
		}
	}
}

func TestValidatePackageIssue(t *testing.T) {
	tests := []struct {
		desc     string
		p        *gpb.VulnerabilityOccurrence_PackageIssue
		wantErrs bool
	}{
		{
			desc: "missing affected CPE URI, want error(s)",
			p: &gpb.VulnerabilityOccurrence_PackageIssue{
				AffectedPackage: "foobar",
				AffectedVersion: &gpb.Version{
					Name: "1.1.2",
					Kind: gpb.Version_NORMAL,
				},
				FixedCpeUri:  "cpe:/o:debian:debian_linux:7",
				FixedPackage: "foobar",
				FixedVersion: &gpb.Version{
					Name: "2.0.0",
					Kind: gpb.Version_NORMAL,
				},
			},
			wantErrs: true,
		},
		{
			desc: "missing affected package, want error(s)",
			p: &gpb.VulnerabilityOccurrence_PackageIssue{
				AffectedCpeUri: "cpe:/o:debian:debian_linux:7",
				AffectedVersion: &gpb.Version{
					Name: "1.1.2",
					Kind: gpb.Version_NORMAL,
				},
				FixedCpeUri:  "cpe:/o:debian:debian_linux:7",
				FixedPackage: "debian",
				FixedVersion: &gpb.Version{
					Name: "2.0.0",
					Kind: gpb.Version_NORMAL,
				},
			},
			wantErrs: true,
		},
		{
			desc: "missing affected version, want error(s)",
			p: &gpb.VulnerabilityOccurrence_PackageIssue{
				AffectedCpeUri:  "cpe:/o:debian:debian_linux:7",
				AffectedPackage: "foobar",
				FixedCpeUri:     "cpe:/o:debian:debian_linux:7",
				FixedPackage:    "foobar",
				FixedVersion: &gpb.Version{
					Name: "2.0.0",
					Kind: gpb.Version_NORMAL,
				},
			},
			wantErrs: true,
		},
		{
			desc: "invalid affected version, want error(s)",
			p: &gpb.VulnerabilityOccurrence_PackageIssue{
				AffectedCpeUri:  "cpe:/o:debian:debian_linux:7",
				AffectedPackage: "foobar",
				AffectedVersion: &gpb.Version{},
				FixedCpeUri:     "cpe:/o:debian:debian_linux:7",
				FixedPackage:    "foobar",
				FixedVersion: &gpb.Version{
					Name: "2.0.0",
					Kind: gpb.Version_NORMAL,
				},
			},
			wantErrs: true,
		},
		{
			desc: "missing fixed version, want error(s)",
			p: &gpb.VulnerabilityOccurrence_PackageIssue{
				AffectedCpeUri:  "cpe:/o:debian:debian_linux:7",
				AffectedPackage: "debian",
				AffectedVersion: &gpb.Version{
					Name: "1.1.2",
					Kind: gpb.Version_NORMAL,
				},
			},
			wantErrs: true,
		},
		{
			desc: "invalid fixed version, want error(s)",
			p: &gpb.VulnerabilityOccurrence_PackageIssue{
				AffectedCpeUri:  "cpe:/o:debian:debian_linux:7",
				AffectedPackage: "debian",
				AffectedVersion: &gpb.Version{
					Name: "1.1.2",
					Kind: gpb.Version_NORMAL,
				},
				FixedVersion: &gpb.Version{},
			},
			wantErrs: true,
		},
		{
			desc: "fixed version NORMAL, but missing fixed CPE URI, want error(s)",
			p: &gpb.VulnerabilityOccurrence_PackageIssue{
				AffectedCpeUri:  "cpe:/o:debian:debian_linux:7",
				AffectedPackage: "foobar",
				AffectedVersion: &gpb.Version{
					Name: "1.1.2",
					Kind: gpb.Version_NORMAL,
				},
				FixedPackage: "debian",
				FixedVersion: &gpb.Version{
					Name: "2.0.0",
					Kind: gpb.Version_NORMAL,
				},
			},
			wantErrs: true,
		},
		{
			desc: "fixed version NORMAL, but missing fixed package, want error(s)",
			p: &gpb.VulnerabilityOccurrence_PackageIssue{
				AffectedCpeUri:  "cpe:/o:debian:debian_linux:7",
				AffectedPackage: "foobar",
				AffectedVersion: &gpb.Version{
					Name: "1.1.2",
					Kind: gpb.Version_NORMAL,
				},
				FixedCpeUri: "cpe:/o:debian:debian_linux:7",
				FixedVersion: &gpb.Version{
					Name: "2.0.0",
					Kind: gpb.Version_NORMAL,
				},
			},
			wantErrs: true,
		},

		{
			desc: "valid package issue with fixed version NORMAL, want success",
			p: &gpb.VulnerabilityOccurrence_PackageIssue{
				AffectedCpeUri:  "cpe:/o:debian:debian_linux:7",
				AffectedPackage: "foobar",
				AffectedVersion: &gpb.Version{
					Name: "1.1.2",
					Kind: gpb.Version_NORMAL,
				},
				FixedCpeUri:  "cpe:/o:debian:debian_linux:7",
				FixedPackage: "foobar",
				FixedVersion: &gpb.Version{
					Name: "2.0.0",
					Kind: gpb.Version_NORMAL,
				},
			},
			wantErrs: false,
		},
		{
			desc: "valid package issue with fixed version MAXIMUM, want success",
			p: &gpb.VulnerabilityOccurrence_PackageIssue{
				AffectedCpeUri:  "cpe:/o:debian:debian_linux:7",
				AffectedPackage: "foobar",
				AffectedVersion: &gpb.Version{
					Name: "1.1.2",
					Kind: gpb.Version_NORMAL,
				},
				FixedVersion: &gpb.Version{
					Kind: gpb.Version_MAXIMUM,
				},
			},
			wantErrs: false,
		},
	}

	for _, tt := range tests {
		errs := validatePackageIssue(tt.p)
		t.Logf("%q: error(s): %v", tt.desc, errs)
		if len(errs) == 0 && tt.wantErrs {
			t.Errorf("%q: validatePackageIssue(%+v): got success, want error(s)", tt.desc, tt.p)
		}
		if len(errs) > 0 && !tt.wantErrs {
			t.Errorf("%q: validatePackageIssue(%+v): got error(s) %v, want success", tt.desc, tt.p, errs)
		}
	}
}
